package com.fxkj.cloud.drms.infra.sql.TDengine;

import com.fxkj.cloud.drms.infra.config.TDengineJdbcTemplateConfig;
import com.fxkj.cloud.drms.infra.utils.BufferedRateExecutor;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.taosdata.jdbc.TSDBPreparedStatement;
import com.zaxxer.hikari.HikariPoolMXBean;
import com.zaxxer.hikari.pool.HikariProxyConnection;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public abstract class TDEngineAbstractDao {

    protected final TDEngineBufferedRateReadExecutor readExecutor;

    protected final TDEngineBufferedRateWriteExecutor writeExecutor;

    protected final TDengineJdbcTemplateConfig config;
    protected static ConcurrentHashMap<PreparedStatement, Connection> map = new ConcurrentHashMap<>();

    protected TDEngineAbstractDao(TDEngineBufferedRateReadExecutor readExecutor, TDEngineBufferedRateWriteExecutor writeExecutor, TDengineJdbcTemplateConfig tdengineJdbcTemplateConfig) {
        this.readExecutor = readExecutor;
        this.writeExecutor = writeExecutor;
        this.config = tdengineJdbcTemplateConfig;
    }


    protected TSDBPreparedStatement prepare(String query) {
        return createPreparedStatement(query);
    }

    private TSDBPreparedStatement createPreparedStatement(String sql) {
        TSDBPreparedStatement preparedStatement;
        try {
            HikariProxyConnection connection = (HikariProxyConnection) config.getConnection();
            if (connection == null) {
                HikariPoolMXBean poolMXBean = config.hikariDataSource.getHikariPoolMXBean();
                log.warn("hikariPool total [{}],active [{}],idle [{}],awaiting [{}]",
                        poolMXBean.getTotalConnections(),
                        poolMXBean.getActiveConnections(),
                        poolMXBean.getIdleConnections(),
                        poolMXBean.getThreadsAwaitingConnection()
                );
                throw new SQLException();
            }
            preparedStatement = connection
                    .prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT)
                    .unwrap(TSDBPreparedStatement.class);
            map.put(preparedStatement, connection);
            return preparedStatement;

        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
            throw new RuntimeException("Failed to create PreparedStatement", sqlException);
        }
    }

    protected TDEngineResultSetFuture<ResultSet> executeAsyncDataBaseOperation(UUID rootOrganizationId, PreparedStatement preStmt) {
        return executeAsync(rootOrganizationId, preStmt, readExecutor);
    }

    protected TDEngineResultSetFuture<ResultSet> executeAsyncRead(UUID rootOrganizationId, PreparedStatement preStmt) {
        return executeAsync(rootOrganizationId, preStmt, readExecutor);
    }

    protected TDEngineResultSetFuture<Boolean> executeAsyncWrite(UUID rootOrganizationId, PreparedStatement preStmt) {
        return executeAsync(rootOrganizationId, preStmt, writeExecutor);
    }

    private <T> TDEngineResultSetFuture<T> executeAsync(UUID rootOrganizationId, PreparedStatement preStmt,
                                                        BufferedRateExecutor<TDEngineStatementTask<T>, TDEngineResultSetFuture<T>> rateExecutor) {
        return rateExecutor.submit(new TDEngineStatementTask<T>(rootOrganizationId, preStmt));
    }


    public void closeConnection(PreparedStatement prep) {
        if (prep == null) {
            return;
        }
        if (map.containsKey(prep)) {
            Connection connection = map.get(prep);
            try {
                prep.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
//            config.hikariDataSource.evictConnection();
//            map.remove(prep);
        }
    }

    protected void closeConnectionCallback(ListenableFuture<?> future, PreparedStatement preparedStatement) {
        Futures.addCallback(future, new FutureCallback<Object>() {
            @Override
            public void onSuccess(Object result) {
                closeConnection(preparedStatement);
            }

            @Override
            public void onFailure(Throwable t) {
                closeConnection(preparedStatement);
            }
        }, MoreExecutors.directExecutor());
    }

}
